package cn.hutool.osc.utils;

import cn.hutool.core.codec.Base64;
import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.convert.Convert;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.util.*;
import cn.hutool.crypto.digest.DigestUtil;
import cn.hutool.osc.config.OscConfig;
import cn.hutool.osc.miniodto.MinioFileInfo;
import cn.hutool.osc.miniodto.MinioListResult;
import cn.hutool.osc.miniodto.MinioObjectStat;
import cn.hutool.osc.miniodto.MinioPartInfo;
import io.minio.*;
import io.minio.http.Method;
import io.minio.messages.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.multipart.MultipartFile;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.s3.S3Client;
import software.amazon.awssdk.services.s3.S3ClientBuilder;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.net.URI;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;


@Slf4j
public class MinioUtils {


    public MinioClient minioClient;
    public S3Client s3Client;
    private final String GbucketName;
    private final String Gregion;
    public MinioUtils(OscConfig config) {
        minioClient = MinioClient.builder()
                .endpoint(config.getEndpoint())
                .credentials(config.getAccessKey(), config.getSecretKey())
                .build();

        GbucketName = config.getBucketName();
        Gregion = config.getRegion();
    }

    public MinioUtils(OscConfig config, S3ClientBuilder builder) {
        Region region = Region.of(config.getRegion());
        AwsBasicCredentials awsCreds = AwsBasicCredentials.create(
                config.getAccessKey(), // AK，即Access Key ID
                config.getSecretKey() // SK，即Secret Access Key
        );
        URI endpointuri = URI.create(config.getEndpoint());
        s3Client = builder.region(region)
                .credentialsProvider(StaticCredentialsProvider.create(awsCreds))
                .endpointOverride(endpointuri)
                .build();
        GbucketName = config.getBucketName();
        Gregion = config.getRegion();
    }

    public void shutdown() {
        try {
            minioClient.close();
            s3Client.close();
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
    /********************* 桶操作 **********/
    /**
     * 判断桶是否存在
     *
     * @param bucketName 桶名
     * @return true/false
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     */
    public Boolean isBucketExists(String bucketName) {
        Boolean bucketExists;
        try {
            bucketExists = minioClient.bucketExists(BucketExistsArgs.builder()
                    .bucket(bucketName)
                    .build());
        } catch (Exception e) {
            log.error("检查桶是否存在时发生错误: " + e.getMessage());
            return false;
        }
        return bucketExists;
    }

    public Boolean isBucketExists() {
        return isBucketExists(GbucketName);
    }

    /**
     * 创建桶
     *
     * @param bucketName 桶名
     * @return true/false
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     */
    public Boolean createBucket(String bucketName) {
        try {
            if (isBucketExists(bucketName)) {
                log.info("桶已经存在");
                return false;
            } else {
                minioClient.makeBucket(MakeBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
            }
        } catch (Exception e) {
            log.error("创建桶时发生错误: " + e.getMessage());
            return false;
        }
        return true;
    }

    public Boolean createBucket() {
        return createBucket(GbucketName);
    }

    /**
     * 删除桶
     *
     * @param bucketName 桶名
     * @return true/false
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     */
    public Boolean deleteBucket(String bucketName) {
        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法删除");
            } else if (!isBucketEmpty(bucketName)) {
                log.info("桶不为空，无法删除");
            } else {
                minioClient.removeBucket(RemoveBucketArgs.builder()
                        .bucket(bucketName)
                        .build());
                return true;
            }
        } catch (Exception e) {
            log.error("删除桶时发生错误: " + e.getMessage());
        }
        return false;
    }

    public Boolean deleteBucket() {
        return deleteBucket(GbucketName);

    }

    private Boolean isBucketEmpty(String bucketName) {
        Iterable<Result<Item>> results = minioClient.listObjects(
                ListObjectsArgs.builder().bucket(bucketName).maxKeys(1).build());

        if (results.iterator().hasNext()) {
            return false;
        } else {
            return true;
        }
    }

    /**
     * 列出所有桶
     *
     * @return 桶名列表
     */
    public List<String> listBuckets() {
        try {
            List<String> buckets = minioClient.listBuckets().stream()
                    .map(Bucket::name)
                    .collect(Collectors.toList());
            return buckets;
        } catch (Exception e) {
            log.error("列出桶时发生错误: " + e.getMessage());
        }
        return Collections.emptyList();
    }

    /**
     * 获取桶信息
     **/
    public SseConfiguration getBucketInfo(String bucketName, GetBucketEncryptionArgs args) {
        try {
            return minioClient.getBucketEncryption(args.builder().bucket(bucketName).build());

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public LifecycleConfiguration getBucketInfo(String bucketName, GetBucketLifecycleArgs args) {
        try {
            return minioClient.getBucketLifecycle(args.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public NotificationConfiguration getBucketInfo(String bucketName, GetBucketNotificationArgs args) {
        try {
            return minioClient.getBucketNotification(args.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public VersioningConfiguration getBucketInfo(String bucketName, GetBucketVersioningArgs args) {
        try {
            return minioClient.getBucketVersioning(args.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public ReplicationConfiguration getBucketInfo(String bucketName, GetBucketReplicationArgs args) {
        try {
            return minioClient.getBucketReplication(args.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public Tags getBucketInfo(String bucketName) {
        try {
            return minioClient.getBucketTags(GetBucketTagsArgs.builder().bucket(bucketName).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置桶信息
     **/
    public void setBucketInfo(String bucketName, SseConfiguration setType) {
        try {
            minioClient.setBucketEncryption(SetBucketEncryptionArgs.builder()
                    .bucket(bucketName)
                    .config(setType).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setBucketInfo(String bucketName, LifecycleConfiguration setType) {
        try {
            minioClient.setBucketLifecycle(SetBucketLifecycleArgs.builder()
                    .bucket(bucketName)
                    .config(setType).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setBucketInfo(String bucketName, NotificationConfiguration setType) {
        try {
            minioClient.setBucketNotification(SetBucketNotificationArgs.builder()
                    .bucket(bucketName)
                    .config(setType).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setBucketInfo(String bucketName, VersioningConfiguration setType) {
        try {
            minioClient.setBucketVersioning(SetBucketVersioningArgs.builder()
                    .bucket(bucketName)
                    .config(setType).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setBucketInfo(String bucketName, ReplicationConfiguration setType) {
        try {
            minioClient.setBucketReplication(SetBucketReplicationArgs.builder()
                    .bucket(bucketName)
                    .config(setType).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    public void setBucketInfo(String bucketName, Tags setType) {
        try {
            minioClient.setBucketTags(SetBucketTagsArgs.builder()
                    .bucket(bucketName)
                    .tags(setType).build());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * 设置桶策略，获取桶策略，删除桶策略
     */
    public Boolean setBucketPolicy(String bucketName, String policyJson) {
        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法设置策略");
                return false;
            }

            minioClient.setBucketPolicy(SetBucketPolicyArgs.builder()
                    .bucket(bucketName)
                    .config(policyJson)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("无法设置策略: " + e.getMessage());
        }
        return false;
    }

    public String getBucketPolicy(String bucketName) {
        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法获取策略");
                return null;
            }
            GetBucketPolicyArgs getBucketPolicyArgs = GetBucketPolicyArgs.builder()
                    .bucket(bucketName)
                    .build();
            String policyJson = minioClient.getBucketPolicy(getBucketPolicyArgs);
            log.info("策略: " + policyJson);
            return policyJson;
        } catch (Exception e) {
            log.error("无法获取策略: " + e.getMessage());
        }
        return null;
    }

    public Boolean deleteBucketPolicy(String bucketName) {
        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法删除策略");
            }

            minioClient.deleteBucketPolicy(DeleteBucketPolicyArgs.builder()
                    .bucket(bucketName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("无法删除策略: " + e.getMessage());
        }
        return false;
    }

    /********************* 文件操作 **********/
    /********************* 下载操作 **********/
    /**
     * 简单下载，直接下载单文件
     *
     * @param objectName 文件名
     * @param bucketName 桶名
     * @param response   响应
     * @return true/false
     */
    public void simpleDownload(String bucketName, String objectName, HttpServletResponse response) {
        try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(objectName)
                .build());
             OutputStream outputStream = response.getOutputStream()) {

            // 设置响应头
            response.setContentType("application/octet-stream");
            response.setHeader("Content-Disposition", "attachment; filename=\"" + objectName + "\"");

            // 将inputStream的数据复制到outputStream
            byte[] buffer = new byte[8192];
            int bytesRead;
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, bytesRead);
            }
            outputStream.flush();
        } catch (Exception e) {
            log.error("下载文件时发生错误: " + e.getMessage(), e);
        }
    }

    /**
     * 断点下载，根据请求头的range下载
     * <img src="images/range.png"/>
     *
     * @param bucketName
     * @param objectName
     * @param request
     * @param response
     */
    public void checkpointDownload(String bucketName, String objectName, HttpServletRequest request, HttpServletResponse response) {
        long objectSize = 0;
        if (isObjectExists(bucketName, objectName) != null) {
            objectSize = isObjectExists(bucketName, objectName).getSize();
        } else {
            log.error("对象不存在: " + objectName);
        }
        String rangeHeader = request.getHeader("Range");
        try {
            long start = 0;
            long end = Long.MAX_VALUE;

            if (rangeHeader != null) {
                Pattern pattern = Pattern.compile("bytes=(\\d*)-(\\d*)");
                Matcher matcher = pattern.matcher(rangeHeader);
                if (matcher.matches()) {
                    String startPart = matcher.group(1);
                    String endPart = matcher.group(2);

                    if (!startPart.isEmpty()) {
                        start = Long.parseLong(startPart);
                    }
                    if (!endPart.isEmpty()) {
                        end = Long.parseLong(endPart);
                    }
                    if (end > objectSize - 1) {
                        end = objectSize - 1;
                    }
                }
                response.setStatus(HttpServletResponse.SC_PARTIAL_CONTENT);
            }

            response.setHeader("Content-Type", "application/octet-stream");
            response.setHeader("Accept-Ranges", "bytes");
            response.setHeader("Content-Range", "bytes " + start + "-" + end + "/" + objectSize);

            try (InputStream inputStream = minioClient.getObject(
                    GetObjectArgs.builder()
                            .bucket(bucketName)
                            .object(objectName)
                            .offset(start)
                            .length(end - start + 1)
                            .build());
                OutputStream out = response.getOutputStream()) {
                byte[] buffer = new byte[8192];
                int bytesRead;
                while ((bytesRead = inputStream.read(buffer)) != -1) {
                    out.write(buffer, 0, bytesRead);
                }
            }
        } catch (Exception e) {
            log.error("下载文件时发生错误: " + e.getMessage(), e);
            response.setStatus(HttpServletResponse.SC_INTERNAL_SERVER_ERROR);
        }
    }

    /**
     * 下载文件到本地指定位置
     *
     * @param objectName 文件名
     * @param bucketName 桶名
     * @param filePath   本地路径，复杂路径需要用URLUtil.encode(filePath, CharsetUtil.CHARSET_UTF_8)处理才能当参数传递
     * @param override   是否覆盖本地文件
     * @return true/false
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     */
    public Boolean filePathDownload(String filePath, String bucketName, String objectName, Boolean override) {
        try {
            DownloadObjectArgs build = DownloadObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .filename(filePath)
                    .overwrite(override)
                    .build();
            minioClient.downloadObject(build);
            log.info("下载文件成功");
            return true;
        } catch (Exception e) {
            log.error("下载文件时发生错误: " + e.getMessage());
        }
        return false;
    }

    public Boolean filePathDownload(String filePath, String objectName) {
        return filePathDownload(filePath, GbucketName, objectName, false);
    }
    /****************************************/

    /********************* 上传操作 **********/
    /*** 建议上传使用application/octet-stream，避免兼容问题 ***/
    /**
     * 简单上传，直接上传单文件
     *
     * @param bucketName
     * @param objectName
     * @param file       注意配置文件multipart限制
     * @return true/false
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     * 如果上传文件大于5mb，使用application/octet-stream
     */
    public Boolean simpleUpload(String bucketName, String objectName, MultipartFile file) {
        HashMap<String, String> md5Map = new HashMap<>();
        try (InputStream fis = file.getInputStream()) {
            String md5 = DigestUtil.md5Hex(fis);
            md5Map.put("fileMd5", md5);
            PutObjectArgs build = PutObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .contentType(file.getContentType())
                    .userMetadata(md5Map)
                    .build();
            minioClient.putObject(build);
            return true;
        } catch (Exception e) {
            log.error("上传文件时发生错误: " + e.getMessage());
        }
        return false;
    }

    public Boolean simpleUpload(String objectName, MultipartFile file) {
        return simpleUpload(GbucketName, objectName, file);
    }

    /**
     * 追加新内容上传，适用于问本，不计算md5
     *
     * @param bucketName
     * @param targetObjectName
     * @param file
     * @return true/false
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     */
    public Boolean appendUpload(String bucketName, String targetObjectName, MultipartFile file) {
        try (InputStream inputStream = minioClient.getObject(GetObjectArgs.builder()
                .bucket(bucketName)
                .object(targetObjectName)
                .build());
             ByteArrayOutputStream outputStream = new ByteArrayOutputStream()) {
            // 读取现有对象内容并写入输出流
            byte[] buffer = new byte[1024];
            int len;
            while ((len = inputStream.read(buffer)) != -1) {
                outputStream.write(buffer, 0, len);
            }

            // 追加新文件内容，分批次写入
            outputStream.write("\n".getBytes());
            try (InputStream fileInputStream = file.getInputStream()) {
                while ((len = fileInputStream.read(buffer)) != -1) {
                    outputStream.write(buffer, 0, len);
                }
            }

            // 使用新的内容直接上传到Minio，无需删除旧对象
            try (InputStream finalInputStream = new ByteArrayInputStream(outputStream.toByteArray())) {
                minioClient.putObject(PutObjectArgs.builder()
                        .bucket(bucketName)
                        .object(targetObjectName)
                        .stream(finalInputStream, finalInputStream.available(), -1)
                        .contentType(file.getContentType())
                        .build());
            }

            return true;
        } catch (Exception e) {
            log.error("追加上传文件时发生错误: " + e.getMessage(), e);
            return false;
        }
    }

    public Boolean appendUpload(String targetObjectName, MultipartFile file) {
        return appendUpload(GbucketName, targetObjectName, file);
    }

    /**
     * 上传本地文件到桶中，可指定分片大小。minio默认大于5mb的文件自动分片上传
     *
     * @param filePath   本地文件路径，需要用URLUtil.encode(filePath, CharsetUtil.CHARSET_UTF_8)
     * @param partSize   分片大小
     * @param objectName
     * @param bucketName
     * @return true/false
     */
    public Boolean filePathUpload(String filePath, Long partSize, String objectName, String bucketName) {
        if (partSize < 5 * 1024 * 1024) {
            log.error("分片大小不能小于5MB");
            return false;
        }
        HashMap<String, String> md5Map = new HashMap<>();
        try (InputStream fis = new FileInputStream(filePath)) {
            String md5 = DigestUtil.md5Hex(fis);
            md5Map.put("fileMd5", md5);
            ObjectWriteResponse objectWriteResponse = minioClient.uploadObject(UploadObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .filename(filePath, partSize)
                    .userMetadata(md5Map)
                    .build());
            if (objectWriteResponse != null && objectWriteResponse.etag() != null) {
                return true;
            } else {
                log.error("上传文件失败");
                return false;
            }
        } catch (Exception e) {
            log.error("上传文件时发生错误: " + e.getMessage());
        }
        return false;
    }

    public Boolean filePathUpload(String filePath, String objectName) {
        return filePathUpload(filePath, (long) 5 * 1024 * 1024, objectName, GbucketName);
    }

    /**
     * 分片or断点上传
     * getUploadId -> multipartUpload -> composeMultipartUpload
     */
    /**
     * 上传分片，仅支持分片小于5mb的分片
     *
     * @param tmpPath       分片上传的临时路径，若不指定，则默认在tmpFilePart/uploadId/下
     * @param minioPartInfo 分片信息
     * @param file          分片文件
     * @param isMd5         是否开启md5校验，不开启为普通分片上传，开启为断点续传上传
     * @return true/false
     * minioPartInfo和file，需要使用RequestPart注解
     * minioPartInfo 需要content-type为application/json
     */
    public Boolean multipartUpload(String tmpPath, MinioPartInfo minioPartInfo, MultipartFile file, Boolean isMd5) {
        try {
            if (isMd5 && ObjectUtil.isNotEmpty(file)) {
                String fileMd5 = DigestUtil.md5Hex(file.getInputStream());
                if (StrUtil.isEmpty(minioPartInfo.getPartMd5()) || !minioPartInfo.getPartMd5().equals(fileMd5)) {
                    log.error("上传分片{} MD5 异常，请检查", minioPartInfo.getCurrentNum());
                    return false;
                }
                MinioObjectStat minioObjectStat = isObjectExists(minioPartInfo.getBucketName(), tmpPath + minioPartInfo.getObjectName());
                if (minioObjectStat != null && minioObjectStat.getEtag() != null && minioObjectStat.getEtag().equals(minioPartInfo.getPartMd5())) {
                    log.info("分片{}已存在，无需上传", minioPartInfo.getCurrentNum());
                    return true;
                } else {
                    log.info("获取对象失败，因为分片{}不存在，开始上传分片", minioPartInfo.getCurrentNum());
                }
            }
            Map<String, String> userMetadata = new HashMap<>();
            userMetadata.put("uploadId", minioPartInfo.getUploadId());
            userMetadata.put("currentNum", minioPartInfo.getCurrentNum());
            userMetadata.put("totalNum", minioPartInfo.getTotalNum());
            userMetadata.put("partMd5", minioPartInfo.getPartMd5());

            PutObjectArgs.Builder builder = PutObjectArgs.builder()
                    .bucket(minioPartInfo.getBucketName())
                    .object(tmpPath + minioPartInfo.getObjectName())
                    .stream(file.getInputStream(), file.getSize(), -1)
                    .userMetadata(userMetadata)
                    .contentType(file.getContentType());

            if (StrUtil.isNotEmpty(minioPartInfo.getBucketName())) {
                builder.bucket(minioPartInfo.getBucketName());
            } else {
                builder.bucket(GbucketName);
            }
            minioClient.putObject(builder.build());
            log.info("分片{}上传成功", minioPartInfo.getCurrentNum());
            return true;
        } catch (Exception e) {
            log.error(StrUtil.format("分片{}上传文件时发生错误: {}", minioPartInfo.getCurrentNum(), e.getMessage()));
        }
        return false;
    }

    public Boolean multipartUpload(MinioPartInfo minioPartInfo, MultipartFile file, Boolean isMd5) {
        return multipartUpload("tmpFilePart/" + minioPartInfo.getUploadId() + "/", minioPartInfo, file, isMd5);
    }

    /**
     * 合并分片上传
     *
     * @param tmpPath       分片上传的临时路径，若不指定，则默认在tmpFilePart/uploadId/下
     * @param minioFileInfo
     * @param isMd5         是否开启md5校验，不开启为普通分片上传，开启为断点续传上传
     * @return
     */
    public Boolean composeMultipartUpload(String tmpPath, MinioFileInfo minioFileInfo, Boolean isMd5) {
        // 获取所有分片的元数据信息
        List<MinioObjectStat> minioObjectStats = listObjects(minioFileInfo.getBucketName(), tmpPath);

        Map<String, String> userMetadata = new HashMap<>();

        // 检查目标对象是否已经存在
        MinioObjectStat objectExists = isObjectExists(minioFileInfo.getBucketName(), minioFileInfo.getObjectName());
        if (isMd5 && objectExists != null) {
            String expectedEtag = objectExists.getUserMetadata().get("etag");
            if (objectExists.getEtag() != null && objectExists.getEtag().equals(expectedEtag)) {
                log.info("合并对象已存在且MD5匹配，无需合并");
                return true; // 直接返回，表示无需再次合并,秒传
            }
        } else if (objectExists == null) {
            List<String> partMd5 = minioObjectStats.stream()
                    .map(item -> item.getUserMetadata().get("X-Amz-Meta-Partmd5"))
                    .collect(Collectors.toList());
            String expectedEtag = calculateETag(partMd5, partMd5.size());
            userMetadata.put("ETag", expectedEtag);
            log.info("合并对象不存在，开始合并分片");
            // 检查分片数量是否一致
            if (Convert.toInt(minioFileInfo.getTotalNum()) != minioObjectStats.size()) {
                log.error(String.format("分片数量不一致，合并失败，原始分片数量：%s，查询到分片数量：%s", minioFileInfo.getTotalNum(), minioObjectStats.size()));
                return false;
            }
        }


        try {
            // 构建ComposeObjectArgs
            List<ComposeSource> sources = minioObjectStats.stream()
                    .map(item -> ComposeSource.builder()
                            .bucket(item.getBucket())
                            .object(item.getObject())
                            .build())
                    .collect(Collectors.toList());

            ComposeObjectArgs composeObjectArgs = ComposeObjectArgs.builder()
                    .bucket(minioFileInfo.getBucketName())
                    .object(minioFileInfo.getObjectName())
                    .sources(sources)
                    .userMetadata(userMetadata)
                    .build();
            // 合并分片
            minioClient.composeObject(composeObjectArgs);

            // 删除临时文件
            if (deleteObjectList(minioFileInfo.getBucketName(), minioObjectStats.stream().map(MinioObjectStat::getObject).collect(Collectors.toList()))) {
                log.info("合并分片成功，删除临时分片成功");
            }
            return true;
        } catch (Exception e) {
            log.error("合并分片上传文件时发生错误: " + e.getMessage());
        }
        return false;
    }

    public Boolean composeMultipartUpload(MinioFileInfo minioFileInfo, Boolean isMd5) {
        return composeMultipartUpload("tmpFilePart/" + minioFileInfo.getUploadId() + "/", minioFileInfo, isMd5);
    }

    /**
     * 获取上传ID
     *
     * @param bucketName
     * @param objectName
     * @return 上传ID
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     * 返回uploadId(根据时间戳，桶名，文件名生成生成base64，并随机截取前30位)
     */
    public String getUploadId(String bucketName, String objectName) {
        if (!isBucketExists(bucketName)) {
            log.info("桶不存在，无法获取上传ID");
            return null;
        }
        String timestamp = Convert.toStr(DateUtil.currentSeconds());
        String rawId = IdUtil.simpleUUID() + "_" + bucketName + "_" + objectName + "_" + timestamp;
        String uploadId = Base64.encode(rawId, CharsetUtil.UTF_8).substring(0, RandomUtil.randomInt(20, 30));
        return uploadId;
    }

    public String getUploadId(String objectName) {
        return getUploadId(GbucketName, objectName);
    }

    /**
     * 计算ETag
     * 公式为ETag = md5(part1md5+part2md5+...)-n
     *
     * @param partMd5List 分片的md5列表
     * @param n           分片数量
     * @return ETag
     * 解释：Etag为文件内容的校验码，用于校验文件完整性
     */
    public String calculateETag(List<String> partMd5List, int n) {
        // 连接所有分片的MD5字节数组
        byte[] combinedMd5 = new byte[16 * n];
        for (int i = 0; i < n; i++) {
            byte[] partMd5 = HexUtil.decodeHex(partMd5List.get(i));
            System.arraycopy(partMd5, 0, combinedMd5, i * partMd5.length, partMd5.length);
        }

        // 计算合并后的MD5值
        byte[] finalMd5 = DigestUtil.md5(combinedMd5);

        // 转换为十六进制字符串
        String hexMd5 = HexUtil.encodeHexStr(finalMd5);

        // 最终的ETag格式
        return hexMd5 + "-" + n;
    }

    /****************************************/

    /**
     * 查询单文件
     *
     * @param bucketName
     * @param objectName
     * @return ObjectStat 文件信息
     * 解释：不提供bucketName，默认使用当前yml配置的桶
     */
    public MinioObjectStat isObjectExists(String bucketName, String objectName) {
        try {
            // 获得对象的元数据。
            StatObjectArgs.Builder argsBuilder = StatObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName);
            StatObjectResponse objectStat = minioClient.statObject(argsBuilder.build());
            if (objectStat != null) {
                return new MinioObjectStat(
                        objectStat.object(),
                        objectStat.bucket(),
                        objectStat.etag(),
                        objectStat.size(),
                        objectStat.lastModified(),
                        objectStat.retentionMode(),
                        objectStat.retentionRetainUntilDate(),
                        objectStat.legalHold(),
                        objectStat.deleteMarker(),
                        objectStat.userMetadata());
            } else {
                log.info("暂无该对象");
                return null;
            }
        } catch (Exception e) {
            log.error("获取对象失败: " + e.getMessage());
        }
        return null;
    }

    public MinioObjectStat isObjectExists(String objectName) {
        return isObjectExists(GbucketName, objectName);
    }

    /**
     * 查询文件列表
     *
     * @param bucketName
     * @param objectNameList
     * @return
     */
    public List<MinioObjectStat> listObjects(String bucketName, List<String> objectNameList) {
        if (!isBucketExists(bucketName) || objectNameList.isEmpty()) {
            log.info("桶不存在或对象列表为空");
            return Collections.emptyList();
        }
        List<MinioObjectStat> collect = objectNameList.stream()
                .map(item -> isObjectExists(bucketName, item))
                .collect(Collectors.toList());
        return collect;
    }

    /**
     * 查询文件列表
     *
     * @param bucketName
     * @param prefix     前缀
     * @param maxKeys    最大数量
     * @param recursive  是否递归
     * @return
     */
    public List<MinioObjectStat> listObjects(String bucketName, String prefix, int maxKeys, Boolean recursive) {
        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法列出对象");
                return Collections.emptyList();
            }
            return fetchObjects(bucketName, prefix, maxKeys, recursive);
        } catch (Exception e) {
            log.error("列出所有对象时发生错误: " + e);
            return Collections.emptyList();
        }
    }

    public List<MinioObjectStat> listObjects(String bucketName, String prefix, int maxKeys) {
        return listObjects(bucketName, prefix, maxKeys, true);
    }

    public List<MinioObjectStat> listObjects(String bucketName, String prefix) {
        return listObjects(bucketName, prefix, 0, true);
    }

    public List<MinioObjectStat> listObjects(String bucketName) {
        return listObjects(bucketName, null, 0, true);
    }

    private List<MinioObjectStat> fetchObjects(String bucketName, String prefix, int maxKeys, Boolean recursive) throws Exception {
        List<MinioObjectStat> objectList = new ArrayList<>();
        ListObjectsArgs.Builder argsBuilder = ListObjectsArgs.builder().bucket(bucketName);
        if (prefix != null) {
            argsBuilder.prefix(prefix);
        }
        if (maxKeys > 0) {
            argsBuilder.maxKeys(maxKeys);
        }
        if (recursive) {
            argsBuilder.recursive(recursive);
        }
        argsBuilder.includeUserMetadata(true);
        Iterable<Result<Item>> results = minioClient.listObjects(argsBuilder.build());
        Iterator<Result<Item>> iterator = results.iterator();
        while (iterator.hasNext()) {
            Item item = iterator.next().get();
            MinioObjectStat.Owner owner = null;
            if (item.owner() == null) {
                owner = new MinioObjectStat.Owner(null, null);
            }
            MinioObjectStat minioObjectStat = new MinioObjectStat(
                    bucketName,
                    item.objectName(),
                    item.etag(),
                    item.size(),
                    item.userMetadata(),
                    item.lastModified(),
                    owner);
            objectList.add(minioObjectStat);
        }
        return objectList;
    }

    /**
     * 删除单/多文件/某个目录下的所有文件，由于minio删除操作拿不到返回值，只能用Boolen表示是否成功
     *
     * @param bucketName
     * @param objectName
     * @return true/false
     */
    public Boolean deleteObject(String bucketName, String objectName) {
        try {
            if (isObjectExists(bucketName, objectName) == null) {
                log.info("对象不存在，无法删除");
                return false;
            }
            minioClient.removeObject(RemoveObjectArgs.builder()
                    .bucket(bucketName)
                    .object(objectName)
                    .build());
            return true;
        } catch (Exception e) {
            log.error("删除对象失败: " + e.getMessage());
        }
        return false;
    }

    public Boolean deleteObjectList(String bucketName, List<String> objectNames) {

        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法删除");

            }
            // 有问题 能删除当时无法得到返回 先不写
            RemoveObjectsArgs build = RemoveObjectsArgs.builder()
                    .bucket(bucketName)
                    .objects(objectNames.stream().map(item -> new DeleteObject(item)).collect(Collectors.toList()))
                    .build();
            Iterable<Result<DeleteError>> results = minioClient.removeObjects(build);
            for (Result<DeleteError> result : results) {
                DeleteError error = result.get();
                log.info("Unable to delete object " + error.objectName() + "; " + error.message());
            }
            return true;
        } catch (Exception e) {
            log.error("删除对象失败: " + e.getMessage());
        }
        return false;
    }

    public Boolean deleteObjectDir(String bucketName, String prefix) {
        try {
            if (!isBucketExists(bucketName)) {
                log.info("桶不存在，无法删除");
            }
            ListObjectsArgs.Builder argsBuilder = ListObjectsArgs.builder().bucket(bucketName).prefix(prefix);
            Iterable<Result<Item>> results = minioClient.listObjects(argsBuilder.build());
            List<String> objectNames = new ArrayList<>();
            for (Result<Item> result : results) {
                Item item = result.get();
                objectNames.add(item.objectName());
            }
            if (CollUtil.isEmpty(objectNames)) {
                log.info("目录下无文件，无法删除");
                return false;
            }
            return deleteObjectList(bucketName, objectNames);
        } catch (Exception e) {
            log.error("删除目录失败: " + e.getMessage());
        }
        return false;
    }

    /**
     * 复制文件到指定桶，复制多文件到指定桶，复制桶（不可与源桶一致）
     * 单文件限制大小5g
     *
     * @param sourceBucketName 源桶名
     * @param sourceObjectName 源文件名
     * @param targetBucketName 目标桶名
     * @param targetObjectName 目标文件名
     * @return true/false
     */
    public Boolean copyObject(String sourceBucketName, String sourceObjectName, String targetBucketName, String targetObjectName) {
        try {
            if (isObjectExists(sourceBucketName, sourceObjectName) == null) {
                log.info("源对象不存在");
                return false;
            }
            if (!isBucketExists(targetBucketName)) {
                log.info("目标桶不存在");
                return false;
            }
            CopySource copySource = CopySource.builder().bucket(sourceBucketName).object(sourceObjectName).build();
            CopyObjectArgs copyObjectArgs = CopyObjectArgs.builder()
                    .source(copySource)
                    .bucket(targetBucketName)
                    .object(targetObjectName)
                    .build();
            minioClient.copyObject(copyObjectArgs);
            return true;
        } catch (Exception e) {
            log.error("复制对象失败: " + e.getMessage());
        }
        return false;
    }

    public List<MinioListResult> copyObjectList(String sourceBucketName, List<String> sourceObjectName, String targetBucketName) {
        List<MinioListResult> minioCopyObjectResultArrayList = new ArrayList<>();
        sourceObjectName.forEach(item -> minioCopyObjectResultArrayList.add(new MinioListResult(item, copyObject(sourceBucketName, item, targetBucketName, item))));
        return minioCopyObjectResultArrayList;
    }

    public List<MinioListResult> copyBucket(String sourceBucketName, String targetBucketName) {
        List<MinioListResult> minioCopyObjectResultArrayList = new ArrayList<>();
        listObjects(sourceBucketName).stream()
                .map(MinioObjectStat::getObject)
                .forEach(item -> minioCopyObjectResultArrayList.add(new MinioListResult(item, copyObject(sourceBucketName, item, targetBucketName, item))));
        return minioCopyObjectResultArrayList;
    }

    /**
     * 复制某个目录到其他目录，目录下所有文件都复制到目标目录下
     */
    public List<MinioListResult> copyObjectDir(String sourceBucketName, String sourceDir, String targetBucketName, String targetDir) {
        try {
            if (!isBucketExists(sourceBucketName)) {
                log.info("源桶不存在");
                return Collections.emptyList();
            }
            if (!isBucketExists(targetBucketName)) {
                log.info("目标桶不存在");
                return Collections.emptyList();
            }
            List<MinioObjectStat> minioObjectStats = listObjects(sourceBucketName, sourceDir);
            List<MinioListResult> minioCopyObjectResultArrayList = new ArrayList<>();
            for (MinioObjectStat minioObjectStat : minioObjectStats) {
                String objectName = minioObjectStat.getObject();
                String targetObjectName = targetDir + objectName.substring(sourceDir.length());
                minioCopyObjectResultArrayList.add(new MinioListResult(objectName, copyObject(sourceBucketName, objectName, targetBucketName, targetObjectName)));
            }
            return minioCopyObjectResultArrayList;
        } catch (Exception e) {
            log.error("复制目录失败: " + e.getMessage());

        }
        return Collections.emptyList();
    }

    /********************* 签名url操作 **********/
    /**
     * 生成带签名的下载url
     *
     * @param duration       有效时间
     * @param unit           时间单位
     * @param method         请求方法，GET下载文件url,PUT上传url
     * @param minioPartInfos 文件信息
     * @param isTemp         是否临时url，临时url会在有效时间后失效，用于上传启动，不支持md5检验
     * @param tempPath       临时url保存路径
     * @return
     */
    public List<String> getPresignedUrl(int duration, TimeUnit unit,
                                        String method, List<MinioPartInfo> minioPartInfos,
                                        Boolean isTemp, String tempPath) {
        if (CollUtil.isEmpty(minioPartInfos)) {
            return Collections.emptyList();
        }
        List<String> collect = minioPartInfos.stream().map(
                minioPartInfoItem -> {
                    try {

                        GetPresignedObjectUrlArgs.Builder build = GetPresignedObjectUrlArgs.builder()
                                .bucket(minioPartInfoItem.getBucketName())
                                .expiry(duration, unit);
                        if ("PUT".equals(method)) {
                            build.method(Method.PUT);
                        } else if ("GET".equals(method)) {
                            build.method(Method.GET);
                        } else {
                            throw new RuntimeException("只能是PUT或者GET");
                        }
                        if (isTemp) {
                            String objectName = tempPath + minioPartInfoItem.getUploadId() + "/" + minioPartInfoItem.getObjectName();
                            build.object(objectName);
                        } else {
                            build.object(minioPartInfoItem.getObjectName());
                        }
                        return minioClient.getPresignedObjectUrl(build.build());
                    } catch (Exception e) {
                        log.error("获取签名URL时发生错误: " + e.getMessage());
                    }
                    return null;
                }
        ).collect(Collectors.toList());
        return collect;
    }

    public List<String> getPresignedUrl(String method, List<MinioPartInfo> minioPartInfos) {
        return getPresignedUrl(5, TimeUnit.MINUTES, method, minioPartInfos, false, null);
    }


}

